home *** CD-ROM | disk | FTP | other *** search
/ Amiga Format CD 7 / Amiga Format AFCD07 (Dec 1996, Issue 91).iso / serious / shareware / comms / internet / html-related / hsc / source / hsclib / eval.c < prev    next >
C/C++ Source or Header  |  1996-08-17  |  38KB  |  1,469 lines

  1. /*
  2.  * hsclib/eval.c
  3.  *
  4.  * attribute value evaluation functions
  5.  *
  6.  * Copyright (C) 1995,96  Thomas Aglassinger
  7.  *
  8.  * This program is free software; you can redistribute it and/or modify
  9.  * it under the terms of the GNU General Public License as published by
  10.  * the Free Software Foundation; either version 2 of the License, or
  11.  * (at your option) any later version.
  12.  *
  13.  * This program is distributed in the hope that it will be useful,
  14.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16.  * GNU General Public License for more details.
  17.  *
  18.  * You should have received a copy of the GNU General Public License
  19.  * along with this program; if not, write to the Free Software
  20.  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21.  *
  22.  * updated: 17-Aug-1995
  23.  * created: 11-Oct-1995
  24.  */
  25.  
  26. #define NOEXTERN_HSCLIB_EVAL_H
  27.  
  28. #include <ctype.h>
  29. #include <time.h>
  30.  
  31. #include "hsclib/inc_base.h"
  32.  
  33. #include "hsclib/eval.h"
  34. #include "hsclib/input.h"
  35. #include "hsclib/skip.h"
  36. #include "hsclib/uri.h"
  37.  
  38. /* maximul length of an operator identifer */
  39. #define MAX_OP_LEN 8
  40.  
  41. /* unknown operator */
  42. #define OP_NONE 0
  43.  
  44. /* step-size for temp. string */
  45. #define TMP_STEPSIZE 128
  46.  
  47. /*
  48.  * equation operators
  49.  */
  50. #define OP_EQ_STR  "="
  51. #define OP_NEQ_STR "<>"
  52. #define OP_GT_STR  ">"
  53. #define OP_LT_STR  "<"
  54. #define OP_GTE_STR ">="
  55. #define OP_LTE_STR "<="
  56. #define OP_CEQ_STR "=="         /* TODO: case sensitive string comparison */
  57. #define OP_INSIDE_STR "IN"      /* TODO: check for substring */
  58. #define OP_CL_BRAKET_STR ")"    /* closing braket */
  59.  
  60. #define OP_EQ   1
  61. #define OP_NEQ  2
  62. #define OP_GT   3
  63. #define OP_LT   4
  64. #define OP_GTE  5
  65. #define OP_LTE  6
  66. #define OP_CEQ  7
  67. #define OP_INSIDE 8
  68. #define OP_CL_BRAKET  10
  69.  
  70. /*
  71.  * boolean operators
  72.  */
  73. #define OP_AND_STR "AND"
  74. #define OP_NOT_STR "NOT"
  75. #define OP_OR_STR  "OR"
  76. #define OP_XOR_STR "XOR"
  77.  
  78. #define OP_AND 11
  79. #define OP_NOT 12
  80. #define OP_OR  13
  81. #define OP_XOR 14
  82.  
  83. /*
  84.  * string operators
  85.  */
  86. #define OP_CAT_STR "+"
  87. #define OP_CAT      15
  88.  
  89. typedef BYTE op_t;
  90.  
  91. /*
  92.  * forward references
  93.  */
  94. STRPTR eval_expression(HSCPRC * hp, HSCATTR * dest, STRPTR endstr);
  95.  
  96. /*
  97.  * 
  98.  * global funcs
  99.  *
  100.  */
  101.  
  102. /*
  103.  * err_op: unknown binary operator
  104.  */
  105. static VOID err_op(HSCPRC * hp, STRPTR opstr)
  106. {
  107.     hsc_message(hp, MSG_UNKN_BINOP, "unknown binary operator %q", opstr);
  108. }
  109.  
  110. /*
  111.  * eval_boolstr
  112.  */
  113. static BOOL eval_boolstr(STRPTR s)
  114. {
  115.     if (s[0])
  116.         return (TRUE);
  117.     else
  118.         return (FALSE);
  119. }
  120.  
  121. /* check for empty brakets (after GetTime/GetGmTime) */
  122. static VOID check_brakets(HSCPRC * hp)
  123. {
  124.     if (parse_wd(hp, "("))
  125.         parse_wd(hp, ")");
  126. }
  127.  
  128. /*
  129.  * gettimestr
  130.  */
  131. static EXPSTR *gettimestr(HSCPRC * hp, struct tm *time)
  132. {
  133. #define TIMEBUF_INC 20
  134.     STRPTR timefmt = get_vartext_byname(hp->defattr, TIMEFORMAT_ATTR);
  135.     EXPSTR *timebuf = init_estr(TIMEBUF_INC);
  136.     BOOL strftrc = 0;           /* result of strftime() */
  137.     size_t i;                   /* loop var */
  138.  
  139.     /* set default time format */
  140.     if (!timefmt)
  141.         timefmt = "%d-%b-%Y, %H:%M";
  142.  
  143.     while (!(hp->fatal) && !strftrc)
  144.     {
  145.         /* expand timebuffer */
  146.         for (i = 0; i < TIMEBUF_INC; i++)
  147.             app_estrch(timebuf, '.');
  148.  
  149.         D(fprintf(stderr, DHL "    timebuf: inc+%d\n", TIMEBUF_INC));
  150.  
  151.         /* output time */
  152.         strftrc = strftime(estr2str(timebuf), estrlen(timebuf),
  153.                            timefmt, time);
  154.     }
  155.  
  156.     if (!strftrc)
  157.     {
  158.         del_estr(timebuf);
  159.         timebuf = NULL;
  160.     }
  161.  
  162.     return (timebuf);
  163. }
  164.  
  165. /*
  166.  * getfilesize
  167.  *
  168.  * get size of a specific file
  169.  *
  170.  * templates for HSC.FORMAT.FILESIZE:
  171.  * %b   size in byte
  172.  * %k   size in Kbyte
  173.  * %m   size in MByte
  174.  * %g   size in Gbyte
  175.  * %a   size, unit computed automatically
  176.  * %u   unit for %A (none, "K", "M" or "G")
  177.  */
  178. static STRPTR getfilesize(HSCPRC * hp, EXPSTR * dest, STRPTR uri)
  179. {
  180.     STRPTR filesizestr = NULL;
  181.     FILE *file = NULL;
  182.     LONG filesize = 0;          /* filesize in byte */
  183.     LONG filesize_k = 0;        /* filesize in Kbyte */
  184.     LONG filesize_m = 0;        /* filesize in Mbyte */
  185.     LONG filesize_g = 0;        /* filesize in Gbyte */
  186.     LONG filesize_auto = 0;     /* filesize in auto-units (%A) */
  187.     EXPSTR *efilename = init_estr(32);
  188.     STRPTR filename = NULL;
  189.     STRPTR sizeunit = "";
  190.     STRPTR s = get_vartext_byname(hp->defattr,
  191.                                   FILESIZEFORMAT_ATTR);
  192.  
  193.     conv_hscuri2file(hp, efilename, uri);
  194.     filename = estr2str(efilename);
  195.  
  196.     D(fprintf(stderr, DHL "  GETFILESIZE(`%s')\n", filename));
  197.     errno = 0;
  198.     file = fopen(filename, "r");
  199.     if (file)
  200.     {
  201.         /* retrieve size */
  202.         fseek(file, 0L, SEEK_END);
  203.         filesize = ftell(file);
  204.         fclose(file);
  205.  
  206.         /* compute size in units, */
  207.         filesize_k = (filesize + 512) >> 10;
  208.         filesize_m = (filesize_k + 512) >> 10;
  209.         filesize_g = (filesize_m + 512) >> 10;
  210.  
  211.         /* compute auto-size */
  212.         if (filesize_g > 10)
  213.         {
  214.             filesize_auto = filesize_g;
  215.             sizeunit = "G";
  216.         }
  217.         else if (filesize_m > 10)
  218.         {
  219.             filesize_auto = filesize_m;
  220.             sizeunit = "M";
  221.         }
  222.         else if (filesize_k > 10)
  223.         {
  224.             filesize_auto = filesize_k;
  225.             sizeunit = "K";
  226.         }
  227.         else
  228.         {
  229.             filesize_auto = filesize;
  230.             sizeunit = "";
  231.         }
  232.     }
  233.     else
  234.     {
  235.         /* file not found */
  236.         filesize = 0;
  237.         filesize_k = 0;
  238.         filesize_m = 0;
  239.         filesize_g = 0;
  240.         filesize_auto = 0;
  241.         sizeunit = "";
  242.         hsc_msg_nouri(hp, filename, uri, "get filesize");
  243.     }
  244.  
  245.     /* parse template */
  246.     clr_estr(dest);
  247.     if (s)
  248.     {
  249.         while (s[0])
  250.         {
  251.             if (s[0] == '%')
  252.             {
  253.                 if (s[1])
  254.                     s++;
  255.                 switch (s[0])
  256.                 {
  257.                 case 'b':
  258.                     app_estr(dest, long2str(filesize));
  259.                     break;
  260.  
  261.                 case 'k':
  262.                     app_estr(dest, long2str(filesize_k));
  263.                     break;
  264.  
  265.                 case 'm':
  266.                     app_estr(dest, long2str(filesize_m));
  267.                     break;
  268.  
  269.                 case 'g':
  270.                     app_estr(dest, long2str(filesize_g));
  271.                     break;
  272.  
  273.                 case 'a':
  274.                     app_estr(dest, long2str(filesize_auto));
  275.                     break;
  276.  
  277.                 case 'u':
  278.                     app_estr(dest, sizeunit);
  279.                     break;
  280.  
  281.                 default:
  282.                     app_estrch(dest, '%');
  283.                     app_estrch(dest, s[0]);
  284.                     break;
  285.                 }
  286.             }
  287.             else
  288.                 app_estrch(dest, s[0]);
  289.             s++;
  290.         }
  291.     }
  292.     else
  293.     {
  294.         D(panic("no template for filesize-format"));
  295.     }
  296.  
  297.     /* set filesize-str */
  298.     filesizestr = estr2str(dest);
  299.     D(fprintf(stderr, DHL "  =`%s'\n", filesizestr));
  300.  
  301.     del_estr(efilename);
  302.  
  303.     return (filesizestr);
  304. }
  305.  
  306. /*
  307.  * check_attrname
  308.  *
  309.  * check string for legal attribute name
  310.  */
  311. BOOL check_attrname(HSCPRC * hp, STRPTR name)
  312. {
  313.     BOOL ok = FALSE;
  314.  
  315.     if (hsc_normch(name[0]))
  316.         ok = TRUE;
  317.     else
  318.         hsc_message(hp, MSG_ILLG_ATTRNAME,
  319.                     "illegal attribute identifier %q", name);
  320.  
  321.     return (ok);
  322. }
  323.  
  324. /*
  325.  * eval_attrname
  326.  *
  327.  * read next word and check it for a legal
  328.  * attribute identifier
  329.  */
  330. static STRPTR eval_attrname(HSCPRC * hp)
  331. {
  332.     STRPTR result = NULL;
  333.     STRPTR nw = infgetw(hp->inpf);
  334.  
  335.     if (nw)
  336.     {
  337.         if (check_attrname(hp, nw))
  338.             result = nw;
  339.     }
  340.     else
  341.         hsc_msg_eof(hp, "attribute identifier expected");
  342.  
  343.     return (result);
  344. }
  345.  
  346. /*
  347.  * quotestr
  348.  *
  349.  * return readable string for quote-kind
  350.  */
  351. #define DOUBLE_QUOTE '\"'
  352. #define SINGLE_QUOTE '\''
  353. STRPTR quotestr(int quote)
  354. {
  355.     STRPTR s = "UNKNOWN";
  356.  
  357.     if (quote == '\"')
  358.         s = "[double]";
  359.     else if (quote == '\'')
  360.         s = "[single]";
  361.     else if (quote == VQ_NO_QUOTE)
  362.         s = "[none]";
  363.     else
  364.         panic("unknown quote-kind");
  365.  
  366.     return (s);
  367. }
  368.  
  369. /*
  370.  * choose_quote
  371.  *
  372.  * choose quote to be used for attr, depending on
  373.  * hp->quotemode and quotes used inside the value
  374.  */
  375. static VOID choose_quote(HSCPRC * hp, HSCATTR * attr)
  376. {
  377.     int quote = attr->quote;
  378.     LONG qm = hp->quotemode;    /* lazy.. */
  379.     BOOL single_quote = FALSE;
  380.     BOOL double_quote = FALSE;
  381.     BOOL nasty_char = FALSE;
  382.  
  383.     STRPTR value = get_vartext(attr);
  384.  
  385.     D(fprintf(stderr, DHL "  choosing quote\n"));
  386.  
  387.     /* scan attribute value for quotes */
  388.     while (value[0])
  389.     {
  390.         if (value[0] == SINGLE_QUOTE)
  391.         {
  392.             D(fprintf(stderr, DHL "    single quote detected\n"));
  393.             single_quote = TRUE;
  394.             nasty_char = TRUE;
  395.         }
  396.         else if (value[0] == DOUBLE_QUOTE)
  397.         {
  398.             D(fprintf(stderr, DHL "    double quote detected\n"));
  399.             double_quote = TRUE;
  400.             nasty_char = TRUE;
  401.         }
  402.         else if (!hsc_normch(value[0]) && !nasty_char)
  403.         {
  404.             D(fprintf(stderr, DHL "    nasty-char #%d detected\n", value[0]));
  405.             nasty_char = TRUE;
  406.         }
  407.  
  408.         value++;
  409.     }
  410.  
  411.     if (qm == QMODE_KEEP)
  412.     {
  413. #if 0                           /* TODO: enable this */
  414.         /* check, if quote is missing */
  415.         if ((attr->quote == VQ_NO_QUOTE)
  416.             && nasty_char)
  417.         {
  418.             hsc_message(hp, MSG_REQU_QUOTE,
  419.                         "value for %A requires quotes", attr);
  420.         }
  421. #endif
  422.     }
  423.     else
  424.     {
  425.         /* choose quote */
  426.         if (single_quote && double_quote)
  427.         {
  428.             /* both kind of quotes appeared in value:
  429.              * replace double-quotes by """ */
  430.             EXPSTR *newval = init_estr(32);     /* new attribute value */
  431.  
  432.             /* scan old value for `\"', replace them by `"'
  433.              * and store new value in newval */
  434.             value = get_vartext(attr);
  435.             while (value[0])
  436.             {
  437.                 if (value[0] == DOUBLE_QUOTE)
  438.                 {
  439.                     D(fprintf(stderr, DHL "    replace by `"' in value\n"));
  440.                     /* TODO: message */
  441.                     app_estr(newval, """);
  442.                 }
  443.                 else
  444.                     app_estrch(newval, value[0]);
  445.                 value++;
  446.             }
  447.  
  448.             /* update attribute value */
  449.             set_vartext(attr, estr2str(newval));
  450.  
  451.             quote = DOUBLE_QUOTE;
  452.  
  453.             del_estr(newval);
  454.         }
  455.         else
  456.         {
  457.             if (single_quote)
  458.             {
  459.                 D(fprintf(stderr, DHL "    double quote forced\n"));
  460.                 quote = DOUBLE_QUOTE;
  461.             }
  462.             else if (double_quote)
  463.             {
  464.                 D(fprintf(stderr, DHL "    single quote forced\n"));
  465.                 quote = SINGLE_QUOTE;
  466.             }
  467.             else
  468.             {
  469.                 /* no quote in value: choose quote user prefers */
  470.                 if (qm == QMODE_SINGLE)
  471.                 {
  472.                     D(fprintf(stderr, DHL "    single quote preferd\n"));
  473.                     quote = SINGLE_QUOTE;
  474.                 }
  475.                 else if (qm == QMODE_DOUBLE)
  476.                 {
  477.                     D(fprintf(stderr, DHL "    double quote prefered\n"));
  478.                     quote = DOUBLE_QUOTE;
  479.                 }
  480.                 else if (qm == QMODE_NONE)
  481.                 {
  482.                     if (nasty_char)
  483.                     {
  484.                         D(fprintf(stderr, DHL "    quote needed (nasty char)\n"));
  485.                         quote = DOUBLE_QUOTE;
  486.                     }
  487.                     else
  488.                     {
  489.                         D(fprintf(stderr, DHL "    no quote needed\n"));
  490.                         quote = VQ_NO_QUOTE;
  491.                     }
  492.                 }
  493.                 else
  494.                 {
  495.                     panic("illegal quote-mode");
  496.                 }
  497.             }
  498.         }
  499.         /* check, if quote has changed */
  500.         if (attr->quote != quote)
  501.         {
  502.             hsc_message(hp, MSG_CHANGED_QUOTE,
  503.                         "changed quotes for %A from %s to %s",
  504.                         attr, quotestr(attr->quote), quotestr(quote));
  505.         }
  506.     }
  507.  
  508.     attr->quote = quote;
  509. }
  510.  
  511. /*
  512.  * try_eval_unary_op
  513.  *
  514.  * reads next word and tries to interpret it as an unary
  515.  * operator; if no fitting operator exists, return NULL,
  516.  * else immediatly process the operator and return its
  517.  * result
  518.  */
  519. static STRPTR try_eval_unary_op(HSCPRC * hp, HSCATTR * dest, BOOL * err)
  520. {
  521.     STRPTR eval_result = NULL;
  522.     INFILE *inpf = hp->inpf;
  523.     STRPTR nw = eval_attrname(hp);
  524.     HSCATTR *tmpdest = new_hscattr(PREFIX_TMPATTR "unary.operator");
  525.  
  526.     tmpdest->vartype = VT_STRING;
  527.  
  528.     *err = FALSE;
  529.     if (nw)
  530.     {
  531.         if (!upstrcmp(nw, "NOT"))
  532.         {
  533.             /* TODO: this part looks a bit stupid... */
  534.             STRPTR nw = infgetw(inpf);
  535.  
  536.             if (nw)
  537.             {
  538.                 BOOL err_rec = FALSE;   /* error var for recursive call */
  539.                 STRPTR endstr = NULL;
  540.  
  541.                 if (strcmp(nw, "("))
  542.                 {
  543.                     /* try to process another unary operator */
  544.                     inungetcw(inpf);
  545.                     eval_result = try_eval_unary_op(hp, dest, &err_rec);
  546.                 }
  547.                 else
  548.                     endstr = ")";
  549.  
  550.                 /* if not, process another expression */
  551.                 if (!eval_result && !err_rec)
  552.                     eval_result = eval_expression(hp, dest, endstr);
  553.             }
  554.             else
  555.                 hsc_msg_eof(hp, "after NOT");
  556.  
  557.             /* set result or return error */
  558.             if (eval_result)
  559.             {
  560.                 set_varbool(dest, !get_varbool(dest));
  561.                 eval_result = get_vartext(dest);
  562.             }
  563.             else
  564.                 *err = TRUE;
  565.         }
  566.         else if (!upstrcmp(nw, "DEFINED"))
  567.         {
  568.             nw = eval_attrname(hp);
  569.             if (nw)
  570.             {
  571.                 HSCATTR *attr = find_varname(hp->defattr, nw);
  572.  
  573.                 if (attr)
  574.                     set_varbool(dest, TRUE);
  575.                 else
  576.                     set_varbool(dest, FALSE);
  577.                 eval_result = get_vartext(dest);
  578.             }
  579.         }
  580.         else if (!upstrcmp(nw, "EXISTS"))
  581.         {
  582.             /* check existence of file */
  583.             eval_result = eval_expression(hp, tmpdest, NULL);
  584.             if (eval_result)
  585.             {
  586.                 FILE *file = NULL;
  587.                 EXPSTR *dest_fname = init_estr(64);
  588.  
  589.                 D(fprintf(stderr, DHL "  EXISTS(`%s')\n", eval_result));
  590.  
  591.                 conv_hscuri2file(hp, dest_fname, eval_result);
  592.                 file = fopen(eval_result, "r");
  593.                 if (file)
  594.                 {
  595.                     fclose(file);
  596.                     set_varbool(dest, TRUE);
  597.                 }
  598.                 else
  599.                     set_varbool(dest, FALSE);
  600.  
  601.                 del_estr(dest_fname);
  602.                 eval_result = get_vartext(dest);
  603.             }
  604.         }
  605.         else if (!upstrcmp(nw, "GETENV"))
  606.         {
  607.             /* get environment variable */
  608.             eval_result = eval_expression(hp, dest, NULL);
  609.             if (eval_result)
  610.             {
  611.                 STRPTR env_value = getenv(get_vartext(dest));
  612.  
  613.                 D(fprintf(stderr, DHL "  GETENV(`%s')\n", eval_result));
  614.                 if (!env_value)
  615.                 {
  616.                     hsc_message(hp, MSG_UNKN_ENVVAR,
  617.                                 "unknown environment variable %q",
  618.                                 get_vartext(dest));
  619.  
  620.                     env_value = "";
  621.                 }
  622.                 set_vartext(dest, env_value);
  623.                 eval_result = get_vartext(dest);
  624.                 D(fprintf(stderr, DHL "  =`%s'\n", eval_result));
  625.             }
  626.         }
  627.         else if (!upstrcmp(nw, "GETFILESIZE"))
  628.         {
  629.             /* retrieve size of a file */
  630.             EXPSTR *filesizestr = init_estr(0);
  631.             HSCATTR *filedestattr = new_hscattr(PREFIX_TMPATTR "get.filesize");
  632.             STRPTR filename = NULL;
  633.  
  634.             eval_result = NULL;
  635.             filedestattr->vartype = VT_STRING;
  636.             filename = eval_expression(hp, filedestattr, NULL);
  637.             if (filename)
  638.             {
  639.                 eval_result = getfilesize(hp, filesizestr, filename);
  640.             }
  641.             if (eval_result)
  642.             {
  643.                 set_vartext(dest, eval_result);
  644.                 eval_result = get_vartext(dest);
  645.             }
  646.             del_hscattr(filedestattr);
  647.             del_estr(filesizestr);
  648.         }
  649.         else if (!upstrcmp(nw, "GETTIME"))
  650.         {
  651.             /* get local time */
  652.             EXPSTR *timestr = gettimestr(hp, localtime(&(hp->start_time)));
  653.  
  654.             D(fprintf(stderr, DHL "  GETTIME\n"));
  655.             if (timestr)
  656.             {
  657.                 set_vartext(dest, estr2str(timestr));
  658.                 del_estr(timestr);
  659.                 eval_result = get_vartext(dest);
  660.             }
  661.             check_brakets(hp);
  662.         }
  663.         else if (!upstrcmp(nw, "GETGMTIME"))
  664.         {
  665.             /* get greenwich mean time */
  666.             EXPSTR *timestr = gettimestr(hp, gmtime(&(hp->start_time)));
  667.  
  668.             D(fprintf(stderr, DHL "  GETGMTIME\n"));
  669.             if (timestr)
  670.             {
  671.                 set_vartext(dest, estr2str(timestr));
  672.                 del_estr(timestr);
  673.                 eval_result = get_vartext(dest);
  674.             }
  675.             check_brakets(hp);
  676.         }
  677.         else if (!upstrcmp(nw, "SET"))
  678.         {
  679.  
  680.             nw = eval_attrname(hp);
  681.             if (nw)
  682.             {
  683.                 HSCATTR *attr = find_varname(hp->defattr, nw);
  684.  
  685.                 if (attr)
  686.                 {
  687.                     if (attr->vartype == VT_BOOL)
  688.                     {
  689.                         set_varbool(dest, get_varbool(attr));
  690.                     }
  691.                     else if (get_vartext(attr))
  692.                         set_varbool(dest, TRUE);
  693.                     else
  694.                         set_varbool(dest, FALSE);
  695.                     eval_result = get_vartext(dest);
  696.                 }
  697.                 else
  698.                 {
  699.                     hsc_msg_unkn_attr(hp, nw);
  700.                     *err = TRUE;
  701.                 }
  702.             }
  703.         }
  704.         else
  705.             inungetcw(inpf);
  706.     }
  707.  
  708.     del_hscattr(tmpdest);
  709.  
  710.     if (!nw)
  711.         *err = TRUE;
  712.  
  713.     return (eval_result);
  714. }
  715.  
  716. /*
  717.  * eval_op
  718.  *
  719.  * evaluate binary operator string
  720.  */
  721. static BYTE eval_op(HSCPRC * hp)
  722. {
  723.     BYTE op = OP_NONE;
  724.     BOOL op_eof = FALSE;        /* flag: end of file reached */
  725.     INFILE *inpf = hp->inpf;
  726.     STRPTR nw = infgetw(inpf);
  727.  
  728.     D(fprintf(stderr, DHL "  operator \"%s", nw));
  729.  
  730.     if (nw)
  731.     {
  732.         /* boolean operators */
  733.         if (!upstrcmp(nw, OP_AND_STR))
  734.             op = OP_AND;
  735.         else if (!upstrcmp(nw, OP_OR_STR))
  736.             op = OP_OR;
  737.         else if (!upstrcmp(nw, OP_XOR_STR))
  738.             op = OP_XOR;
  739.  
  740.         /* concatenation operator */
  741.         else if (!strcmp(nw, OP_CAT_STR))
  742.             op = OP_CAT;
  743.  
  744.         /* closing braket */
  745.         else if (!strcmp(nw, OP_CL_BRAKET_STR))
  746.             op = OP_CL_BRAKET;
  747.  
  748.         /* comparison operators */
  749.         else if (strenum(nw, "<|=|>", '|', STEN_CASE))
  750.         {
  751.             STRARR opstr[3];
  752.             int ch;
  753.  
  754.             /* determine whole comparison operator:
  755.              * take first word, and check for next
  756.              * single character, if it is one of
  757.              * "<", "=" or ">", too. if so, append
  758.              * it to the string that tells the
  759.              * operator.
  760.              */
  761.             strcpy(opstr, nw);
  762.             ch = infgetc(inpf);
  763.             if (ch != EOF)
  764.             {
  765.                 D(fprintf(stderr, "%c", (char) ch));
  766.  
  767.                 if (strchr("<=>", ch))
  768.                 {
  769.                     opstr[1] = ch;
  770.                     opstr[2] = 0;
  771.                 }
  772.                 else
  773.                     inungetc(ch, inpf);
  774.             }
  775.             else
  776.                 op_eof = TRUE;
  777.  
  778.             /* find out comparison operator */
  779.             if (!strcmp(nw, OP_EQ_STR))
  780.                 op = OP_EQ;
  781.             else if (!strcmp(nw, OP_NEQ_STR))
  782.                 op = OP_EQ;
  783.             else if (!strcmp(nw, OP_GT_STR))
  784.                 op = OP_GT;
  785.             else if (!strcmp(nw, OP_LT_STR))
  786.                 op = OP_LT;
  787.             else if (!strcmp(nw, OP_LTE_STR))
  788.                 op = OP_LTE;
  789.             else if (!strcmp(nw, OP_GTE_STR))
  790.                 op = OP_GTE;
  791.             else if (!strcmp(nw, OP_CEQ_STR))
  792.                 op = OP_CEQ;
  793.             else
  794.                 err_op(hp, opstr);
  795.         }
  796.         else
  797.             err_op(hp, nw);
  798.  
  799.     }
  800.     else
  801.         op_eof = TRUE;
  802.  
  803.     D(fprintf(stderr, "\"\n"));
  804.  
  805.     if (op_eof)
  806.         hsc_msg_eof(hp, "operator expected");
  807.  
  808.     return (op);
  809. }
  810.  
  811. /*
  812.  * process_op
  813.  */
  814. static VOID process_op(HSCPRC * hp, HSCATTR * dest, BYTE op, STRPTR str1, STRPTR str2)
  815. {
  816.     EXPSTR *result = init_estr(40);
  817.     BOOL result_set = FALSE;
  818.  
  819.     D(fprintf(stderr, DHL "  \"%s\", \"%s\"\n", str1, str2));
  820.     if (str2 && (op != OP_NONE))
  821.     {
  822.         BOOL bool_val1 = eval_boolstr(str1);
  823.         BOOL bool_val2 = eval_boolstr(str2);
  824.  
  825.         switch (op)
  826.         {
  827.         case OP_AND:
  828.             if (bool_val1 && bool_val2)
  829.                 set_varbool(dest, TRUE);
  830.             else
  831.                 set_varbool(dest, FALSE);
  832.             break;
  833.  
  834.         case OP_OR:
  835.             if (bool_val1 || bool_val2)
  836.                 set_varbool(dest, TRUE);
  837.             else
  838.                 set_varbool(dest, FALSE);
  839.             break;
  840.  
  841.         case OP_XOR:
  842.             if ((bool_val1 || bool_val2)
  843.                 && !(bool_val1 && bool_val2)
  844.                 )
  845.                 set_varbool(dest, TRUE);
  846.             else
  847.                 set_varbool(dest, FALSE);
  848.             break;
  849.  
  850.         case OP_EQ:
  851.  
  852.             /* string comparison, ignore case */
  853.             if (!upstrcmp(str1, str2))
  854.                 set_varbool(dest, TRUE);
  855.             else
  856.                 set_varbool(dest, FALSE);
  857.             break;
  858.  
  859.         case OP_NEQ:
  860.  
  861.             /* string comparison "<>" */
  862.             if (upstrcmp(str1, str2))
  863.                 set_varbool(dest, TRUE);
  864.             else
  865.                 set_varbool(dest, FALSE);
  866.             break;
  867.  
  868.         case OP_GT:
  869.  
  870.             /* string comparison ">" */
  871.             if (upstrcmp(str1, str2) > 0)
  872.                 set_varbool(dest, TRUE);
  873.             else
  874.                 set_varbool(dest, FALSE);
  875.             break;
  876.  
  877.         case OP_LT:
  878.  
  879.             /* string comparison "<" */
  880.             if (upstrcmp(str1, str2) < 0)
  881.                 set_varbool(dest, TRUE);
  882.             else
  883.                 set_varbool(dest, FALSE);
  884.             break;
  885.  
  886.         case OP_GTE:
  887.  
  888.             /* string comparison ">=" */
  889.             if (upstrcmp(str1, str2) >= 0)
  890.                 set_varbool(dest, TRUE);
  891.             else
  892.                 set_varbool(dest, FALSE);
  893.             break;
  894.  
  895.         case OP_LTE:
  896.  
  897.             /* string comparison "<=" */
  898.             if (upstrcmp(str1, str2) <= 0)
  899.                 set_varbool(dest, TRUE);
  900.             else
  901.                 set_varbool(dest, FALSE);
  902.             break;
  903.  
  904.         case OP_CEQ:
  905.  
  906.             /* string comparison, case sensitive */
  907.             if (!strcmp(str1, str2))
  908.                 set_varbool(dest, TRUE);
  909.             else
  910.                 set_varbool(dest, FALSE);
  911.             break;
  912.  
  913.         case OP_CAT:
  914.  
  915.             /* concat two expressions */
  916.             set_estr(result, str1);
  917.             app_estr(result, str2);
  918.             result_set = TRUE;
  919.  
  920.             break;
  921.  
  922.         default:
  923.             panic("empty operator");
  924.             break;
  925.         }
  926.     }
  927.     /* store result in destination attribute,
  928.      * if this has not happened yet
  929.      */
  930.     if (result_set)
  931.         set_vartext(dest, estr2str(result));
  932.  
  933.     /* remove temp. string for result */
  934.     del_estr(result);
  935. }
  936.  
  937. /*
  938.  *-------------------------------------
  939.  * eval_expression: evaluate expression
  940.  *-------------------------------------
  941.  */
  942.  
  943. /*
  944.  * eval_string_expr
  945.  *
  946.  * evaluate string expression WITH enclosing quotes
  947.  */
  948. static STRPTR eval_string_expr(HSCPRC * hp, HSCATTR * dest)
  949. {
  950.     INFILE *inpf = hp->inpf;
  951.     STRPTR eval_result = NULL;
  952.     EXPSTR *tmpstr = init_estr(TMP_STEPSIZE);
  953.     int quote;
  954.  
  955.     /* get quote char */
  956.     quote = infgetc(inpf);
  957.     if (quote != EOF)
  958.     {
  959.         BOOL end = FALSE;
  960.  
  961.         while (!end)
  962.         {
  963.             int ch = infgetc(inpf);
  964.             if (ch == EOF)
  965.             {
  966.                 hsc_msg_eof(hp, "reading string constant");
  967.                 eval_result = NULL;
  968.                 end = TRUE;
  969.             }
  970.             else if (ch != quote)
  971.             {
  972.                 /* check for LF inside string */
  973.                 if (ch == '\n')
  974.                     hsc_message(hp, MSG_STR_LF,
  975.                                 "linefeed found inside string");
  976.  
  977.                 /* append next char to string */
  978.                 app_estrch(tmpstr, ch);
  979.             }
  980.             else
  981.             {
  982.                 /* closing quote reached */
  983.                 eval_result = estr2str(tmpstr);
  984.                 end = TRUE;
  985.             }
  986.         }
  987.     }
  988.     else
  989.         hsc_msg_eof(hp, "reading string constant");
  990.  
  991.     /* set new attribute value */
  992.     if (eval_result)
  993.     {
  994.         /* set new quotes */
  995.         dest->quote = quote;
  996.         /* set new value */
  997.         set_vartext(dest, eval_result);
  998.         eval_result = get_vartext(dest);
  999.     }
  1000.     /* remove temp. string */
  1001.     del_estr(tmpstr);
  1002.  
  1003.     return (eval_result);
  1004. }
  1005.  
  1006. /*
  1007.  * eval_string_expr_noquote
  1008.  *
  1009.  * evaluate string expression WITHOUT enclosing quotes
  1010.  */
  1011. STRPTR eval_string_expr_noquote(HSCPRC * hp, HSCATTR * dest)
  1012. {
  1013.     /* TODO: check for ch==">": attrval missing */
  1014.     INFILE *inpf = hp->inpf;
  1015.     STRPTR eval_result = NULL;
  1016.     EXPSTR *tmpstr = init_estr(TMP_STEPSIZE);
  1017.     BOOL end = FALSE;
  1018.  
  1019.     /* TODO: check for empty expression */
  1020.  
  1021.     /*
  1022.      * read next char from input file until a
  1023.      * closing quote if found.
  1024.      * if the arg had no quote, a white space
  1025.      * or a '>' is used to detect end of arg.
  1026.      * if a LF is found, view error message
  1027.      */
  1028.     while (!end)
  1029.     {
  1030.  
  1031.         int ch = infgetc(inpf);
  1032.         if (ch == EOF)
  1033.         {
  1034.             hsc_msg_eof(hp, "reading attribute");
  1035.  
  1036.             end = TRUE;
  1037.         }
  1038.         else if ((ch == '\n')
  1039.                  || (inf_isws(ch, inpf) || (ch == '>')))
  1040.         {
  1041.             /* end of arg reached */
  1042.             inungetc(ch, inpf);
  1043.             eval_result = estr2str(tmpstr);
  1044.             end = TRUE;
  1045.         }
  1046.         else
  1047.         {
  1048.             /* append next char to tmpstr */
  1049.             app_estrch(tmpstr, ch);
  1050.         }
  1051.     }
  1052.  
  1053.     /* set new attribute value */
  1054.     if (eval_result)
  1055.     {
  1056.         set_vartext(dest, eval_result);
  1057.         eval_result = get_vartext(dest);
  1058.     }
  1059.     /* remove temp. string */
  1060.     del_estr(tmpstr);
  1061.  
  1062.     return (eval_result);
  1063. }
  1064.  
  1065. /*
  1066.  * eval_attrref
  1067.  *
  1068.  * evaluate reference to attribute
  1069.  *
  1070.  */
  1071. static STRPTR eval_attrref(HSCPRC * hp, HSCATTR * destattr)
  1072. {
  1073.     STRPTR eval_result = NULL;
  1074.     STRPTR nw = eval_attrname(hp);
  1075.  
  1076.     if (nw)
  1077.     {
  1078.         HSCATTR *refvar = find_varname(hp->defattr, nw);
  1079.  
  1080.         if (refvar)
  1081.         {
  1082.             /* TODO: type checking */
  1083.             destattr->quote = refvar->quote;
  1084.             eval_result = refvar->text;
  1085.  
  1086.             /* check empty/circular reference */
  1087.             if (!eval_result)
  1088.                 hsc_message(hp, MSG_EMPTY_SYMB_REF,
  1089.                             "empty reference to %A", destattr);
  1090.  
  1091.             /* debugging message */
  1092.             DDA(fprintf(stderr, DHL "   %s refers to (%s)\n",
  1093.                         destattr->name, refvar->name));
  1094.         }
  1095.         else
  1096.         {
  1097.             /* reference to unknown destattr */
  1098.             hsc_msg_unkn_attr(hp, nw);
  1099.         }
  1100.  
  1101.         /* set empty value for reference to NULL */
  1102.         if ((!refvar) || (!eval_result))
  1103.         {
  1104.             /* return empty destattr */
  1105.             destattr->quote = '"';
  1106.             eval_result = "";
  1107.         }
  1108.     }
  1109.  
  1110.     /* set value of destination attribute */
  1111.     if (eval_result)
  1112.         set_vartext(destattr, eval_result);
  1113.  
  1114.     return (eval_result);
  1115. }
  1116.  
  1117. /*
  1118.  * eval_expression
  1119.  *
  1120.  * params: dest....attribute where to store result in
  1121.  *         inpf....input file
  1122.  *         err.....flag that is set to TRUE if an error occured
  1123.  *         endstr..word that is expected at end of expession;
  1124.  *                 NULL for no special ending word
  1125.  * result: result of evaluation (IF_TRUE or FALSE)
  1126.  *
  1127.  * NOTE: if endstr==NULL, that means that eval_expression() is called
  1128.  *   immediatly after the "=" of an attribute. In this case, if no
  1129.  *   quotes are found as next char, all chars are read until the next
  1130.  *   white-space or LF occures.
  1131.  *   If no special char was found until now (only "A".."Z", "_", "-" and
  1132.  *   digits occured), we first interpret the string as name for an
  1133.  *   attribute reference; if such an attribute does not exist, it is
  1134.  *   asumed that the value passed is a string constant (same as it would
  1135.  *   have been enclosed in quotes).
  1136.  *
  1137.  */
  1138. STRPTR eval_expression(HSCPRC * hp, HSCATTR * dest, STRPTR endstr)
  1139. {
  1140.     /* TODO: endstr needs to be boolean flag only */
  1141.     INFILE *inpf = hp->inpf;
  1142.     EXPSTR *vararg = init_estr(TMP_STEPSIZE);
  1143.     /* used as destination by eval_string_exprXX() */
  1144.  
  1145.     STRPTR exprstr = NULL;      /* return value */
  1146.     int ch;                     /* char read from input */
  1147.  
  1148.     /* skip white spaces */
  1149.     infskip_ws(inpf);
  1150.  
  1151.     /* read dest->quote char */
  1152.     ch = infgetc(inpf);
  1153.     if (!strchr(VQ_STR_QUOTE, ch))
  1154.         if (ch != EOF)
  1155.             dest->quote = VQ_NO_QUOTE;
  1156.         else
  1157.             hsc_msg_eof(hp, "reading attribute");
  1158.     else
  1159.         dest->quote = ch;
  1160.  
  1161.     /* skip linefeeds, if evaluation hsc-expression */
  1162.     if (endstr)
  1163.         skip_lfs(hp);
  1164.  
  1165.     if (ch == '(')
  1166.     {
  1167.         /* process braket */
  1168.         exprstr = eval_expression(hp, dest, ")");
  1169.  
  1170.         /* set generic double quote */
  1171.         if (!endstr)
  1172.             dest->quote = '\"';
  1173.     }
  1174.     else if (ch != EOF)
  1175.     {
  1176.         /* write current char read back to input buffer */
  1177.         inungetc(ch, inpf);
  1178.  
  1179.         if (dest->quote != VQ_NO_QUOTE)
  1180.         {
  1181.             /* process string expression with quotes */
  1182.             exprstr = eval_string_expr(hp, dest);
  1183.         }
  1184.         else if (endstr)
  1185.         {
  1186.             BOOL err = FALSE;
  1187.  
  1188.             /* try to process unary operator */
  1189.             exprstr = try_eval_unary_op(hp, dest, &err);
  1190.  
  1191.             /* process attribute reference */
  1192.             if (!exprstr && !err)
  1193.                 exprstr = eval_attrref(hp, dest);
  1194.         }
  1195.         else
  1196.         {
  1197.             /* process string expression without quotes */
  1198.             exprstr = eval_string_expr_noquote(hp, dest);
  1199.         }
  1200.     }
  1201.  
  1202.     if (exprstr && endstr)
  1203.     {
  1204.         BYTE op;
  1205.  
  1206.         skip_lfs(hp);           /* skip linefeeds */
  1207.  
  1208.         /* evaluate operator */
  1209.         op = eval_op(hp);
  1210.  
  1211.         if (op == OP_CL_BRAKET)
  1212.             DMSG("  END mark operator reached");
  1213.         else if (op != OP_NONE)
  1214.         {
  1215.             /* no endmark reached */
  1216.             STRPTR str1 = exprstr;
  1217.  
  1218.             /* read second operator */
  1219.             if (op != OP_NONE)
  1220.             {
  1221.                 STRPTR str2 = NULL;
  1222.                 HSCATTR *dest1 = new_hscattr(PREFIX_TMPATTR "2nd.operand");
  1223.  
  1224.                 /* init dummy attribute */
  1225.                 dest1->vartype = dest->vartype;
  1226.                 dest1->quote = dest->quote;
  1227.  
  1228.                 /* compute second value */
  1229.                 str2 = eval_expression(hp, dest1, endstr);
  1230.  
  1231.                 if (str2)
  1232.                     process_op(hp, dest, op, str1, str2);
  1233.                 else
  1234.                     exprstr = NULL;
  1235.  
  1236.                 /* remove result of second value */
  1237.                 del_hscattr((APTR) dest1);
  1238.             }
  1239.             else
  1240.             {
  1241.                 /* TODO: skip expression until ">" */
  1242.                 exprstr = NULL;
  1243.             }
  1244.  
  1245.             if (exprstr)
  1246.             {
  1247.                 /* store result */
  1248.                 exprstr = get_vartext(dest);
  1249.             }
  1250.         }
  1251.         else
  1252.         {
  1253.             DMSG("  NO operator");
  1254.         }
  1255.     }
  1256.  
  1257.     if (!endstr)
  1258.     {
  1259.         if (exprstr && !endstr)
  1260.         {
  1261.             /*
  1262.              * set quote, depending on quotemode
  1263.              */
  1264.  
  1265. #if 0                           /* TODO:remove */
  1266.             switch (hp->quotemode)
  1267.             {
  1268.  
  1269.             case QMODE_KEEP:
  1270.                 /* do nufin */
  1271.                 break;
  1272.  
  1273.             case QMODE_DOUBLE:
  1274.                 dest->quote = '\"';
  1275.                 break;
  1276.  
  1277.             case QMODE_SINGLE:
  1278.                 dest->quote = '\'';
  1279.                 break;
  1280.  
  1281.             case QMODE_NONE:
  1282.                 dest->quote = VQ_NO_QUOTE;
  1283.                 break;
  1284.  
  1285.             default:
  1286.                 panic("unknown quotemode");
  1287.                 break;
  1288.             }
  1289. #else
  1290.             if ((dest->vartype != VT_BOOL)
  1291.                 && !(dest->varflag & (VF_MACRO | VF_KEEP_QUOTES)))
  1292.             {
  1293.                 choose_quote(hp, dest);
  1294.             }
  1295. #endif
  1296.  
  1297.             /*
  1298.              * check enum type
  1299.              */
  1300.             if (dest->vartype == VT_ENUM)
  1301.             {
  1302.                 if (!strenum(exprstr, dest->enumstr, '|', STEN_NOCASE))
  1303.                 {
  1304.                     /* unknown enum value */
  1305.                     hsc_message(hp, MSG_ENUM_UNKN,
  1306.                                 "unknown value %q for enumerator %A",
  1307.                                 exprstr, dest);
  1308.                 }
  1309.             }
  1310.             /*
  1311.              * check color value
  1312.              */
  1313.             else if (dest->vartype == VT_COLOR)
  1314.             {
  1315.                 BOOL ok = FALSE;
  1316.                 size_t color_len = strlen("#rrggbb");
  1317.  
  1318.                 if (exprstr[0] == '#')
  1319.                 {
  1320.                     /* check RGB-value */
  1321.                     if (strlen(exprstr) == color_len)
  1322.                     {
  1323.                         size_t i = 1;
  1324.  
  1325.                         ok = TRUE;
  1326.                         for (; i < color_len; i++)
  1327.                             if (!isxdigit(exprstr[i]))
  1328.                                 ok = FALSE;
  1329.                     }
  1330.                 }
  1331.                 else
  1332.                 {
  1333.                     /* check color name */
  1334.                     if (hp->color_names)
  1335.                     {
  1336.                         if (strenum(exprstr, hp->color_names, '|', STEN_NOCASE))
  1337.                             ok = TRUE;
  1338.                     }
  1339.                     else
  1340.                         ok = TRUE;
  1341.                 }
  1342.  
  1343.                 if (!ok)
  1344.                     /* illegal color value */
  1345.                     hsc_message(hp, MSG_ILLG_COLOR,
  1346.                                 "illegal color value %q for %A",
  1347.                                 exprstr, dest);
  1348.             }
  1349.             /*
  1350.              * check numeric value
  1351.              */
  1352.             else if (dest->vartype == VT_NUM)
  1353.             {
  1354.                 BOOL ok = FALSE;
  1355.                 int i = 0;
  1356.  
  1357.                 if ((exprstr[0] == '+')
  1358.                     || (exprstr[0] == '-'))
  1359.                 {
  1360.                     i = 1;
  1361.                 }
  1362.                 if (strlen(exprstr) - i)
  1363.                 {
  1364.                     ok = TRUE;
  1365.                     while (exprstr[i] && ok)
  1366.                     {
  1367.                         if (!isdigit(exprstr[i]))
  1368.                             ok = FALSE;
  1369.                         else
  1370.                             i++;
  1371.                     }
  1372.                 }
  1373.                 if (!ok)
  1374.                     /* unknown enum value */
  1375.                     hsc_message(hp, MSG_ILLG_NUM,
  1376.                                 "illegal numeric value %q for %A",
  1377.                                 exprstr, dest);
  1378.             }
  1379.             /*
  1380.              * for boolean attributes, set the name
  1381.              * of the attribute if TRUE, or set an
  1382.              * empty string, if FALSE
  1383.              */
  1384.             else if (dest->vartype == VT_BOOL)
  1385.                 if (eval_boolstr(exprstr))
  1386.                     set_vartext(dest, dest->name);
  1387.                 else
  1388.                     set_vartext(dest, "");
  1389.  
  1390.             /*
  1391.              * checks performed only for tags,
  1392.              * but are skipped for macros
  1393.              */
  1394.             if (!(dest->varflag & VF_MACRO))
  1395.             {
  1396.                 /*
  1397.                  * parse uri (convert abs.uris, check existence)
  1398.                  */
  1399.                 if (dest->vartype == VT_URI)
  1400.                 {
  1401.                     EXPSTR *dest_uri = init_estr(32);
  1402.  
  1403.                     parse_uri(hp, dest_uri, exprstr);
  1404.                     set_vartext(dest, estr2str(dest_uri));
  1405.  
  1406.                     del_estr(dest_uri);
  1407.                 }
  1408.             }
  1409.             exprstr = get_vartext(dest);
  1410.         }
  1411.         else
  1412.         {
  1413.             /* if error occured,
  1414.              * skip until ">", unread ">"
  1415.              */
  1416.             skip_until_eot(hp, NULL);
  1417.             if (!hp->fatal)
  1418.                 inungetcw(inpf);
  1419.         }
  1420.     }
  1421.  
  1422.     del_estr(vararg);
  1423.  
  1424.     return (exprstr);
  1425. }
  1426.  
  1427. /*
  1428.  * eval_cloneattr
  1429.  *
  1430.  * read name of attribute, check if it has been set;
  1431.  * if so, return value of attribute.
  1432.  * all possible errors are handled by this function.
  1433.  *
  1434.  * params: hp.....hsc-process
  1435.  *         dest...detination attribute where to store value
  1436.  * result: value of attribute, if it has been set, or NULL
  1437.  *         if attribute is empty or unknown or other error
  1438.  *         has occured.
  1439.  */
  1440. STRPTR eval_cloneattr(HSCPRC * hp, HSCATTR *dest)
  1441. {
  1442.     STRPTR nw = eval_attrname(hp);
  1443.     STRPTR attrval = NULL;
  1444.  
  1445.     if (nw)
  1446.     {
  1447.         HSCATTR *attr = find_varname(hp->defattr, nw);
  1448.  
  1449.         if (attr)
  1450.         {
  1451.             attrval = get_vartext(attr);
  1452.             dest->quote = attr->quote;
  1453.         }
  1454.         else
  1455.             hsc_msg_unkn_attr(hp, nw);
  1456.     }
  1457.  
  1458.     /* update attribute value and quotes */
  1459.     if (attrval)
  1460.     {
  1461.         set_vartext(dest, attrval);
  1462.         attrval = get_vartext(dest);
  1463.         choose_quote(hp, dest);
  1464.     }
  1465.  
  1466.     return( attrval);
  1467. }
  1468.  
  1469.